using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using ARD.Common;
using SharpCommunications.Cryptography;
using SharpCommunications.Interfaces;
using SharpCommunications.Logging;
using Location = SharpCommunications.Logging.Location;

namespace SharpCommunications.Connection
{
    public class SharpListener : IDisposable
    {
        public delegate void NewConnectionHandlerDelegate(ISharpConnectionEndpoint connectionEndpoint);
        public List<ISharpConnectionEndpoint> Endpoints { get; set; }

        public class ListenerThread : IDisposable
        {
            protected IPAddress ipAddress;
            protected int port;
            protected TcpListener tcpListener;
            public Thread thread;
            protected SharpListener sharpListener;


            public ListenerThread(SharpListener sharpListener, IPAddress ipAddress, int port)
            {
                this.sharpListener = sharpListener;
                this.ipAddress = ipAddress;
                this.port = port;
                tcpListener = new TcpListener(ipAddress, port);

                thread = new Thread(new ThreadStart(Run));
                thread.IsBackground = true;
                thread.Name = string.Format("Listener thread ({0}:{1})", ipAddress, port);
                thread.Start();

            }

            public void Run()
            {
                Location location = new Location("ListenerThread.Run()");
                try
                {
                    tcpListener.Start();

                    while (true)
                    {
                        try
                        {
                            TcpClient tcpClient = tcpListener.AcceptTcpClient();
                            
                            LogFactory.Log.DebugMessage(location, "Accepted connection\n");

                            ISharpConnectionEndpoint connectionEndpoint = new SharpConnectionEndpoint(this.sharpListener.Cryptographer);
                            connectionEndpoint.Initialize(tcpClient);

                            if(this.sharpListener.NewConnection != null)
                            {
                                this.sharpListener.NewConnection(connectionEndpoint);
                            }
                            else
                            {
                                if (this.sharpListener.Endpoints != null)
                                {
                                    this.sharpListener.Endpoints.Add(connectionEndpoint);
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            LogFactory.Log.ErrorMessage(location, ex, true); 
                        }
                    }
                }
                catch (Exception ex)
                {
                    LogFactory.Log.ErrorMessage(location, ex, true);
                }
                finally
                {
                    tcpListener.Stop();
                }
            }


            #region IDisposable Members

            public void Dispose()
            {
                if (this.sharpListener.Endpoints != null)
                {
                    foreach (ISharpConnectionEndpoint connectionEndpoint in sharpListener.Endpoints)
                    {
                        connectionEndpoint.Dispose();
                    }

                    this.sharpListener.Endpoints = null;
                }
            }

            #endregion
        }

        protected List<IPAddress> ipaddresses;
        protected int port;
        public ISharpCryptographer Cryptographer { get; set; }

        public List<ListenerThread> threads = new List<ListenerThread>();
        
        public event NewConnectionHandlerDelegate NewConnection;

        public SharpListener(ISharpCryptographer cryptographer, int port)
        {
            this.Cryptographer = cryptographer;

            this.Endpoints = new List<ISharpConnectionEndpoint>();

            this.port = port;
            this.ipaddresses = new List<IPAddress>();

            string sHostName = Dns.GetHostName();
            IPHostEntry iphost = Dns.GetHostEntry(sHostName);

            for (int k = 0; k < iphost.AddressList.Length; k++)
            {
                IPAddress ipaddr = iphost.AddressList[k];
                this.ipaddresses.Add(ipaddr);
            }

            iphost = Dns.GetHostEntry("localhost");

            for (int k = 0; k < iphost.AddressList.Length; k++)
            {
                IPAddress ipaddr = iphost.AddressList[k];
                this.ipaddresses.Add(ipaddr);
            }
        }

        public SharpListener(ISharpCryptographer cryptographer, int port, params string[] hostnames)
        {
            this.Cryptographer = cryptographer;

            this.Endpoints = new List<ISharpConnectionEndpoint>();

            this.port = port;
            this.ipaddresses = new List<IPAddress>();

            foreach (string sHostName in hostnames)
            {
                IPHostEntry iphost = Dns.GetHostEntry(sHostName);

                for (int k = 0; k < iphost.AddressList.Length; k++)
                {
                    IPAddress ipaddr = iphost.AddressList[k];
                    this.ipaddresses.Add(ipaddr);
                }
            }
        }

        public void Listen()
        {
            Listen(true);
        }

        public void Listen(bool waitForAllListeners)
        {
            foreach (IPAddress ipaddr in this.ipaddresses)
            {
                this.threads.Add(new ListenerThread(this, ipaddr,this.port));
            }

            if (waitForAllListeners)
            {
                this.WaitForAllListeners();
            }
        }

        protected void WaitForAllListeners()
        {
            for (int k = 0; k < this.threads.Count; k++)
            {
                this.threads[k].thread.Join();
            }
        }



        #region IDisposable Members

        public void Dispose()
        {
            Location location = new Location("SharpListener.Dispose()");

            foreach (ListenerThread listener in this.threads)
            {
                try
                {
                    listener.Dispose();
                }
                catch (Exception ex)
                {
                    LogFactory.Log.ErrorMessage(location, ex, true);
                }
            }
        }

        #endregion
    }
}
